<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>MathBox - 3D Grapher</title>
  <script src="mathbox-bundle.js"></script>
  <script src="dat.gui.js"></script>
  
<!-- http://silentmatt.com/javascript-expression-evaluator/ -->
<script src="parser.js"></script>

  <link rel="stylesheet" href="mathbox.css">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1">
</head>
<body>
  <script>
    var mathbox = mathBox({
      plugins: ['core', 'controls', 'cursor', 'mathbox'],
      controls: {klass: THREE.OrbitControls}
    });
    if (mathbox.fallback) throw "WebGL not supported"

    var three = mathbox.three;
    three.renderer.setClearColor(new THREE.Color(0xFFFFFF), 1.0);

	var graphData, view;
	
	var functionText = "sin(a*x*x + b*y*y)";
	
	var pointText = "(1,1)";
	
	var a = 0.75, b = 0.75;
	var	xMin = -3, xMax = 3, yMin = -3,	yMax = 3, zMin = -3, zMax = 3;
	
	var zAutofit = true;
	
	var numPrisms = 20;
	var xLower = 0, xUpper = 2, yLower = 0, yUpper = 2;
	
	// start of updateGraph function ==============================================================
	var updateGraphFunc = function() 
	{ 
		var zFunc = Parser.parse( functionText ).toJSFunction( ['x','y'] );
		graphData.set("expr", 
		    function (emit, x, y, i, j, t) 
			{ 
			    emit( x, zFunc(x,y), y );
			}
		);
		
		if (zAutofit)
		{
			var xStep = (xMax - xMin) / 256;
			var yStep = (yMax - yMin) / 256;
			var zSmallest = zFunc(xMin, yMin);
			var zBiggest  = zFunc(xMin, yMin);
			for (var x = xMin; x <= xMax; x += xStep)
			{
				for (var y = yMin; y <= yMax; y += yStep)
				{
					var z = zFunc(x,y);
					if (z < zSmallest) zSmallest = z;
					if (z > zBiggest)  zBiggest  = z;
				}
			}
			zMin = zSmallest;
			zMax = zBiggest;
		}
		view.set("range", [[xMin, xMax], [zMin,zMax], [yMin, yMax]]); 

		if (graphColorStyle == "Solid")
		{
			// zMax = white, zMin = black
			graphColors.set("expr", 
				function (emit, x, y, i, j, t) 
				{ 
					var z = zFunc(x,y);
					var percent = (z - 1.2 * zMin) / (zMax - 1.2 * zMin);
					var color = new THREE.Color( 0xffffff );
					color.setHSL( 1-percent, 1, 0.5 );
					emit( color.r, color.g, color.b, 1.0 );
				}
			);
			
			graphViewSolid.set("visible", true);
			graphViewSolid.set("opacity", 1.0);
			graphViewSolid.set("zWrite", true);
			graphViewWire.set("visible", true);
		}
		else if (graphColorStyle == "Transparent")
		{
			graphViewSolid.set("visible", true);
			graphViewSolid.set("opacity", 0.5);
			graphViewSolid.set("zWrite", false);
			graphViewWire.set("visible", true);
		}
		else if (graphColorStyle == "Wireframe")
		{
			graphViewSolid.set("visible", false);
			graphViewWire.set("visible", true);
		}
		else if (graphColorStyle == "Invisible")
		{
			graphViewSolid.set("visible", false);
			graphViewWire.set("visible", false);
		}

	    // RIEMANN PRISM data 
		
		shapeDataTop.set("width", numPrisms);
		shapeDataTop.set("height", numPrisms);  		
		shapeDataBottom.set("width", numPrisms);
		shapeDataBottom.set("height", numPrisms);  		
		shapeDataSides.set("width", numPrisms);
		shapeDataSides.set("height", numPrisms);  		
		shapeDataSides2.set("width", numPrisms);
		shapeDataSides2.set("height", numPrisms);  	
		// seems to have no effect when decreasing width value. bug?
		boxColors.set("width", numPrisms);
		boxColors.set("height", numPrisms);  	
		
		originX = xLower;
		deltaX = (xUpper - xLower)/numPrisms;
		originY = yLower;
		deltaY = (yUpper - yLower)/numPrisms;
		var dataTop = [];
		var dataBottom = [];
		var dataSides = [];
		var dataSides2 = [];
		for (var x = originX; x < xUpper; x += deltaX)
		{
			for(var y = originY; y < yUpper; y += deltaY)
			{
				var z = zFunc(x,y);
				var p0 = [x,z,y];
				var p1 = [x+deltaX,z,y];
				var p2 = [x,z,y+deltaY];
				var p3 = [x+deltaX,z,y+deltaY];
				var p4 = [x,0,y];
				var p5 = [x+deltaX,0,y];
				var p6 = [x,0,y+deltaY];
				var p7 = [x+deltaX,0,y+deltaY];
				dataTop.push(p0,p1,p3,p2,p0);
				dataBottom.push(p4,p5,p7,p6,p4);
				dataSides.push(p0,p1,p5,p4,p0);
				dataSides.push(p0,p2,p6,p4,p0);
				dataSides2.push(p3,p1,p5,p7,p3);
				dataSides2.push(p3,p2,p6,p7,p3);
			}
		}
		shapeDataTop.set("data", dataTop);
		shapeDataBottom.set("data", dataBottom);
		shapeDataSides.set("data", dataSides);
		shapeDataSides2.set("data", dataSides2);
		
		// works
		boxColors.set("expr", 
			function(emit, i,j, time)
			{
				var xx = originX + i * deltaX;
				var yy = originY + j * deltaY;
				var zz = zFunc(yy,xx);
					
				var percent = (zz - 1.2 * zMin) / (zMax - 1.2 * zMin);
				var color = new THREE.Color( 0xffffff );
				color.setHSL( 1-percent, 1, 0.5 );
				emit( color.r, color.g, color.b, 1.0 );
			}
		);
		
	} 
	// end of updateGraph function ==============================================================
	
	var updateGraph = function() { updateGraphFunc(); };

	// setting proxy:true allows interactive controls to override base position
	var camera = mathbox.camera( { proxy: true, position: [4,2,4] } );

	 // save as variable to adjust later
    view = mathbox.cartesian(
	  {
        range: [[xMin, xMax], [yMin, yMax], [zMin,zMax]],
        scale: [2,1,2],
      }
	);

	// axes
	var xAxis = view.axis( {axis: 1, width: 8, detail: 40, color:"red"} );
    var xScale = view.scale( {axis: 1, divide: 10, nice:true, zero:true} );
    var xTicks = view.ticks( {width: 5, size: 15, color: "red", zBias:2} );
    var xFormat = view.format( {digits: 2, font:"Arial", weight: "bold", style: "normal", source: xScale} );
    var xTicksLabel = view.label( {color: "red", zIndex: 0, offset:[0,-20], points: xScale, text: xFormat} );
	
	var yAxis = view.axis( {axis: 3, width: 8, detail: 40, color:"green"} );
    var yScale = view.scale( {axis: 3, divide: 5, nice:true, zero:false} );
    var yTicks = view.ticks( {width: 5, size: 15, color: "green", zBias:2} );
    var yFormat = view.format( {digits: 2, font:"Arial", weight: "bold", style: "normal", source: yScale} );
    var yTicksLabel = view.label( {color: "green", zIndex: 0, offset:[0,0], points: yScale, text: yFormat} );
	
	var zAxis = view.axis( {axis: 2, width: 8, detail: 40, color:"blue"} );
    var zScale = view.scale( {axis: 2, divide: 5, nice:true, zero:false} );
    var zTicks = view.ticks( {width: 5, size: 15, color: "blue", zBias:2} );
    var zFormat = view.format( {digits: 2, font:"Arial", weight: "bold", style: "normal", source: zScale} );
    var zTicksLabel = view.label( {color: "blue", zIndex: 0, offset:[0,0], points: zScale, text: zFormat} );
	
	view.grid( {axes:[1,3], width: 2, divideX: 20, divideY: 20, opacity:0.25} );
	
	var graphData = view.area({
		axes: [1,3], channels: 3, width: 64, height: 64,
        expr: function (emit, x, y, i, j, t)
		{
		  var z = x*y;
          emit( x, z, y );
        },
    });
	
	// actuall emitter set later.
	var graphColors = view.area({
		expr: function (emit, x,y, i,j, t) 
		{
			if (x < 0)
				emit(1.0, 0.0, 0.0, 1.0);
		    else
				emit(0.0, 1.0, 0.0, 1.0);
		},
		axes: [1,3],
		width:  64, height: 64,
		channels: 4, // RGBA
    });
	
	// create graph in two parts, because solid and wireframe are different colors
	var graphShaded = false;	
	var graphViewSolid = view.surface({
		points:graphData, 
		color:"#FFFFFF", shaded:false, fill:true, lineX:false, lineY:false, colors:graphColors, visible:false, width:0, opacity:1.0, zWrite:true // need zWrite:false to see other shapes underneath...
	});
	
	var graphWireVisible = true;
	var graphViewWire = view.surface({
		points: graphData,
		color:"#000000", shaded:false, fill:false, lineX:true, lineY:true, visible:graphWireVisible, width:2
    });
	
	// RIEMANN PRISMS
	
	var shapeDataTop = view.matrix({
	    width: numPrisms, height: numPrisms,
		// expr: initialized later
		items: 5, // need 5 points for closed path around rectangle
		channels: 3,
	});
 
	var shapeDataBottom = view.matrix({
	    width: numPrisms, height: numPrisms,
		items: 5, channels: 3,
	});
	
	var shapeDataSides = view.matrix({
	    width: numPrisms, height: numPrisms,
		items: 10, channels: 3,
	});
	
	var shapeDataSides2 = view.matrix({
	    width: numPrisms, height: numPrisms,
		items: 10, channels: 3,
	});
	
	var boxColors = view.matrix({
		width: numPrisms, height: numPrisms,
		// expr: function(emit, i,j, time)
 		channels: 4, // RGBA
    });
	
	// remember to set ""color"" to white because ""colors"" tints
	var shapeViewTopFill = view.face({
		points: shapeDataTop,
		line: false, fill: true, color: "white", colors: boxColors, 
	});
	var shapeViewTopLine = view.face({
		points: shapeDataTop,
		line: true,	fill: false,
		color: "black",	width: 1, opacity: 1.00, stroke: "solid",
	});
	
	var shapeViewBottomFill = view.face({
		points: shapeDataBottom,
		line: false, fill: true, color: "white", colors: boxColors,
	});
	
	var shapeViewSides = view.face({
		points: shapeDataSides,
		line: false, fill: true, color: "gray"
	});
	var shapeViewSidesLine = view.face({
		points: shapeDataSides,
		line: true,	fill: false,
		color: "black",	width: 1, opacity: 1.00, stroke: "solid",
	});
	
	var shapeViewSides2 = view.face({
		points: shapeDataSides2,
		line: false, fill: true, color: "gray"
	});
	var shapeViewSides2Line = view.face({
		points: shapeDataSides2,
		line: true,	fill: false,
		color: "black",	width: 1, opacity: 1.00, stroke: "solid",
	});
	
    // GUI controls
	
	var gui = new dat.GUI();
	
	gui.add( this, 'functionText' ).name('y = f(x) = ');
	
	var graphColorStyle = "Solid";
	var graphColorStyleList = ["Solid", "Transparent", "Wireframe", "Invisible"];
	var graphColorGUI = gui.add( this, "graphColorStyle", graphColorStyleList ).name('Graph style').onChange( updateGraphFunc );

	var graphWireVisibleGUI = gui.add( this, "graphWireVisible" ).name("View wireframe").onChange(
		function()
		{
			graphViewWire.set("visible", graphWireVisible);
		}
	);
	
	var folder0 = gui.addFolder('Parameters');
	var aGUI = folder0.add( this, 'a' ).min(0).max(5).step(0.01).name('a = ');
	var bGUI = folder0.add( this, 'b' ).min(0).max(5).step(0.01).name('b = ');
	folder0.close();
	
	// broken
	// gui.add( this, 'numPrisms' ).min(3).max(30).step(1).name('divisions').onChange( updateGraphFunc );
	
	var folder1 = gui.addFolder('Window Range');
	var xMinGUI = folder1.add( this, 'xMin' );
	var xMaxGUI = folder1.add( this, 'xMax' );
	var zMinGUI = folder1.add( this, 'zMin' ).name("yMin");
	var zMaxGUI = folder1.add( this, 'zMax' ).name("yMax");
	folder1.close();
	
	gui.add( this, 'updateGraph' ).name("Update Graph");
	
	// onChange or onFinishChange
	aGUI.onChange( updateGraphFunc );
	bGUI.onChange( updateGraphFunc );
	
	gui.open();
	
	updateGraphFunc();
	</script>
</body>
</html>
